import json
import numpy as np


class BoundingBox:
    """
    定义矩形框
    """

    def __init__(self, title, confidence, xmin, ymin, xmax, ymax):
        self.title = title
        self.type = 'rectangle'
        self.confidence = float(confidence)
        self.shape = {'points': [[int(xmin), int(ymin)], [int(xmax), int(ymax)]]}

    def to_json(self):
        return {
            'title': self.title,
            'type': self.type,
            'confidence': float(self.confidence),
            'shape': self.shape
        }

    def draw(self, img: np.ndarray, color=[0, 191, 255], label=False, line_thickness=1):
        import cv2
        # 将坐标点转换为tuple
        c1, c2 = tuple(self.shape['points'][0]), tuple(self.shape['points'][1])
        # 绘制多边形
        cv2.rectangle(img, c1, c2, color, thickness=line_thickness, lineType=cv2.LINE_AA)
        if label:
            tf = max(line_thickness - 1, 1)  # font thickness
            t_size = cv2.getTextSize(self.title, 0, fontScale=line_thickness / 3, thickness=tf)[0]
            # cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA)  # filled
            # 标签在左上部
            # cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
            title = str(self.shape['points'][1][0]) + str(self.shape['points'][1][1])
            if label:
                cv2.putText(img, title, (c1[0] - 2, c1[1] - 5), 0, line_thickness / 2, color, thickness=tf,
                            lineType=cv2.LINE_AA)


class Polygon:
    """
    定义多边形框
    """

    def __init__(self, title, confidence, points):
        self.title = title
        self.type = 'polygon'
        self.confidence = float(confidence)
        self.shape = {'points': points}

    def to_json(self):
        return {
            'title': self.title,
            'type': self.type,
            'confidence': float(self.confidence),
            'shape': self.shape
        }

    def draw(self, img: np.ndarray, color=[0, 191, 255], label=False, line_thickness=1):
        import cv2
        # 将坐标点转换为NumPy数组
        pts = np.array(self.shape['points'], dtype=np.int32)
        # 绘制多边形
        cv2.polylines(img, [pts], isClosed=True, color=color, thickness=line_thickness)
        if label:
            tf = max(line_thickness - 1, 1)  # font thickness
            t_size = cv2.getTextSize(self.title, 0, fontScale=line_thickness / 3, thickness=tf)[0]
            # cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA)  # filled
            # 计算多边形的重心
            center = np.mean(self.shape['points'], axis=0, dtype=np.int32)
            if label:
                cv2.putText(img, self.title, tuple(center), 0, line_thickness / 2, color, thickness=tf,
                            lineType=cv2.LINE_AA)


class ModelResult:
    """
    模型结果封装实体类
    status:场景识别状态 True / False;
    object:画框json信息;
    sample_type:样本类型 正样本-P 负样本-N;
    time:响应耗时
    """
    def __init__(self):
        # 场景识别状态： 画框信息
        self.object = []
        # 场景识别状态： True / False
        self.status = False
        # 样本类型，： 正样本-P 正确识别的样本/Positive 负样本-N 错误识别的样本 Negative
        self.sample_type = "N"
        self.custom_data = {}  # 初始化一个字典用于存储自定义键值对
        # 响应耗时
        self.time = -1

    def __getattr__(self, name):
        if name in self.custom_data:
            return self.custom_data[name]
        raise AttributeError(f"'ModelResult' object has no attribute '{name}'")

    def set_negative(self):
        """
        设置为负样本
        :return:
        """
        self.sample_type = "N"

    def set_positive(self):
        """
        设置为正样本
        :return:
        """
        self.sample_type = "P"

    def set_time(self, time_consuming):
        """
        time.time() - time.time()
        配置响应时间
        :param time_consuming: 响应时间(秒)
        """
        # 转换毫秒
        time_consuming = int(time_consuming * 1000)
        self.time = time_consuming

    def add_custom_data(self, key, value):
        self.custom_data[key] = value

    def to_json(self):
        json_data = {
            'status': self.status,
            'sample_type': self.sample_type,
            **self.custom_data,
            'object': self.object_to_json()
        }
        if self.time != -1:
            json_data['time'] = self.time
        return json_data

    def to_string(self):
        return json.dumps(self.to_json())

    def object_to_json(self):
        object_json = [box.to_json() for box in self.object]
        return object_json

    def object_to_string(self):
        return json.dumps(self.object_to_json())

    def draw(self, img: np.ndarray, color=[0, 255, 255], label=False, title=None, line_thickness=1):
        """
        提供图片进行画图，
        :param img: 图片
        :param color: 默认黄色 [255, 191, 0] "red": [0, 0, 255], "green": [0, 255, 0],"blue": [255, 0, 0], [random.randint(0, 255) for _ in range(3)]
        :param label: 是否要在左上角添加标签
        :param title: 是否要在左上角显示文字标题
        :param line_thickness: 宽度默认1
        :return:
        """
        import cv2
        for box in self.object:
            # tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1  # line/font thickness
            # tl = line_thickness
            # color = color or [random.randint(0, 255) for _ in range(3)]
            if isinstance(box, dict):
                if box["type"] == 'rectangle':
                    box = self._dict_to_bounding_box(box)
                else:
                    box = self._dict_to_polyon(box)
            box.draw(img, color=color, label=label)
        if title:
            cv2.putText(img, title, (10, 35), 0, 1, color, thickness=1, lineType=cv2.LINE_AA)

    @staticmethod
    def _dict_to_bounding_box(box):
        title = box.get("title")
        confidence = box.get("confidence")
        shape = box.get("shape")
        xmin, ymin = shape["points"][0]
        xmax, ymax = shape["points"][1]

        return BoundingBox(title, confidence, xmin, ymin, xmax, ymax)

    @staticmethod
    def _dict_to_polyon(box):
        title = box.get("title")
        confidence = box.get("confidence")
        points = box.get("shape")["points"]

        return Polygon(title, confidence, points)
